home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Personal Computer World 2009 February
/
PCWFEB09.iso
/
Software
/
Linux
/
Kubuntu 8.10
/
kubuntu-8.10-desktop-i386.iso
/
casper
/
filesystem.squashfs
/
usr
/
bin
/
pycentral
< prev
next >
Wrap
Text File
|
2008-05-28
|
77KB
|
1,892 lines
#! /usr/bin/python
import fnmatch, glob, os, re, string, sys, time, cStringIO
from optparse import OptionParser
from ConfigParser import SafeConfigParser
sys.path[0:0] = ['/usr/share/pycentral-data', '/usr/share/python']
import pyversions
try:
SetType = set
except NameError:
import sets
SetType = sets.Set
set = sets.Set
program = os.path.basename(sys.argv[0])
shared_base = '/usr/share/pycentral/'
shared_base2 = '/usr/share/pyshared/'
pycentral_version = '0.6.7ubuntu1'
req_pycentral_version = '0.6.7'
def samefs(path1, path2):
if not (os.path.exists(path1) and os.path.exists(path2)):
return False
while path1 != os.path.dirname(path1):
if os.path.ismount(path1):
break
path1 = os.path.dirname(path1)
while path2 != os.path.dirname(path2):
if os.path.ismount(path2):
break
path2 = os.path.dirname(path2)
return path1 == path2
def version2depends(vinfo):
if isinstance(vinfo, set):
vinfo = list(vinfo)
if isinstance(vinfo, list):
vinfo = vinfo[:]
vinfo.sort()
nv = [int(s) for s in vinfo[-1].split('.')]
deps = 'python (>= %s), python (<< %d.%d)' % (vinfo[0], nv[0], nv[1]+1)
elif vinfo in ('all', 'current'):
supported = [d[6:] for d in pyversions.supported_versions()
if re.match(r'python\d\.\d', d)]
supported.sort()
deps = 'python (>= %s)' % supported[0]
elif vinfo == 'current_ext':
cv = pyversions.default_version(version_only=True)
nv = [int(s) for s in cv.split('.')]
deps = 'python (>= %s), python (<< %d.%d)' % (cv, nv[0], nv[1]+1)
else:
raise ValueError, 'unknown version info %s' % vinfo
return deps + ', python-central (>= %s)' % req_pycentral_version
def read_dpkg_status(verbose=False):
"""Read the dpkg status file, return a list of packages depending
on python-central and having a Python-Version information field."""
packages = []
rx = re.compile(r'\bpython-central\b')
pkgname = version = None
depends = ''
status = []
for line in file('/var/lib/dpkg/status'):
if line.startswith('Package:'):
if version != None and 'installed' in status:
if 'python-support' in depends:
pass
elif rx.search(depends):
packages.append((pkgname, version))
if verbose:
print " %s: %s (%s)" % (pkgname, version, status)
version = None
status = []
pkgname = line.split(':', 1)[1].strip()
elif line.startswith('Python-Version:'):
version = line.split(':', 1)[1].strip()
elif line.startswith('Depends:'):
depends = line.split(':', 1)[1].strip()
elif line.startswith('Status:'):
status = line.split(':', 1)[1].strip().split()
if version != None and 'installed' in status:
if rx.search(depends):
packages.append((pkgname, version))
if verbose:
print " %s: %s (%s)" % (pkgname, version, status)
return packages
class PyCentralError(Exception):
"""Python Central Exception"""
pass
class PyCentralVersionMissingError(PyCentralError):
"""Python Central Version Missing Exception"""
pass
class PythonRuntime:
def __init__(self, name, version, interp, prefix):
self.name = name
self.version = version
if name.startswith('python'):
self.short_name = name[6:]
else:
self.short_name = name
self.interp = interp
if prefix.endswith('/'):
self.prefix = prefix + 'site-packages/'
else:
self.prefix = prefix + '/site-packages/'
def byte_compile_dirs(self, dirs, bc_option, exclude=None):
"""call compileall.py -x <exclude regexp> <dirs> according
to bc_options"""
logging.debug('\tbyte-compile directories')
errors = False
cmd = [self.interp, self.prefix + '/compileall.py', '-q']
if exclude:
cmd.extend(['-x', exclude])
cmd.extend(dirs)
for opt in ('standard', 'optimize'):
if not opt in bc_option:
continue
if opt == 'optimize':
cmd[1:1] = ['-O']
rv = os.spawnv(os.P_WAIT, self.interp, cmd[1:])
if rv:
raise PyCentralError
def byte_compile(self, files, bc_option, exclude=None, ignore_errors=False, force=False):
errors = False
if exclude:
rx = re.compile(exclude)
files2 = []
for fn in files:
mo = rx.search(fn)
if mo:
continue
files2.append(fn)
else:
files2 = files
if not files2:
logging.info('\tno files to byte-compile')
return
logging.debug('\tbyte-compile files (%d/%d) %s' \
% (len(files), len(files2), self.name))
debug_files = files2[:min(2, len(files2))]
if len(files2) > 2:
debug_files.append('...')
logging.debug('\t %s' % debug_files)
cmd = [self.interp, '/usr/bin/py_compilefiles', '-q']
if ignore_errors:
cmd.append('-i')
if force:
cmd.append('-f')
cmd.append('-')
for opt in ('standard', 'optimize'):
if not opt in bc_option:
continue
if opt == 'optimize':
cmd[1:1] = ['-O']
try:
import subprocess
p = subprocess.Popen(cmd, bufsize=1,
shell=False, stdin=subprocess.PIPE)
fd = p.stdin
except ImportError:
p = None
fd = os.popen(' '.join(cmd), 'w')
for fn in files2:
fd.write(fn + '\n')
rv = fd.close()
if p:
p.wait()
errors = p.returncode != 0
else:
errors = rv != None
if errors:
raise PyCentralError, 'error byte-compiling files (%d)' % len(files2)
def remove_byte_code(self, files):
errors = False
logging.debug('\tremove byte-code files (%d)' % (len(files)))
for ext in ('c', 'o'):
for fn in files:
fnc = fn + ext
if os.path.exists(fnc):
try:
os.unlink(fnc)
except OSError, e:
print "Sorry", e
errors = True
if errors:
raise PyCentralError, 'error removing the byte-code files'
installed_runtimes = None
default_runtime = None
def get_installed_runtimes(with_unsupported=True):
global installed_runtimes
global default_runtime
if not installed_runtimes:
import glob
installed_runtimes = []
default_version = pyversions.default_version(version_only=True)
supported = pyversions.supported_versions()
old = pyversions.old_versions()
if with_unsupported:
unsupported = pyversions.unsupported_versions()
else:
unsupported = []
for interp in glob.glob('/usr/bin/python[0-9].[0-9]'):
if old and os.path.basename(interp) in old:
print "INFO: using old version '%s'" % interp
elif unsupported and os.path.basename(interp) in unsupported:
print "INFO: using unsupported version '%s'" % interp
if not (os.path.basename(interp) in supported
or (old and os.path.basename(interp) in old)
or (unsupported and os.path.basename(interp) in unsupported)):
continue
version = interp[-3:]
rt = PythonRuntime('python' + version,
version,
'/usr/bin/python' + version,
'/usr/lib/python' + version)
installed_runtimes.append(rt)
if version == default_version:
default_runtime = rt
return installed_runtimes
def get_default_runtime():
get_installed_runtimes()
return default_runtime
def get_runtime_for_version(version):
if version == 'current':
return get_default_runtime()
for rt in get_installed_runtimes():
if rt.version == version:
return rt
return None
debian_config = None
def get_debian_config():
global debian_config
if debian_config is not None:
return debian_config
config = SafeConfigParser()
fn = '/etc/python/debian_config'
if os.path.exists(fn):
try:
config.readfp(open(fn))
except Error:
logging.error("error reading config file `%s'" % fn)
sys.exit(1)
# checks
if not config.has_option('DEFAULT', 'byte-compile'):
config.set('DEFAULT', 'byte-compile', 'standard')
bc_option = config.get('DEFAULT', 'byte-compile')
bc_values = set([v.strip() for v in bc_option.split(',')])
bc_unknown = bc_values - set(['standard', 'optimize'])
if bc_unknown:
sys.stderr.write("%s: `%s': unknown values `%s'"
" in `byte-compile option'\n"
% (program, fn, ', '.join(list(bc_unknown))))
sys.exit(1)
config.set('DEFAULT', 'byte-compile', ', '.join(bc_values))
if config.has_option('DEFAULT', 'overwrite-local'):
val = config.get('DEFAULT', 'overwrite-local').strip().lower()
overwrite_local = val in ('yes', '1', 'true')
else:
overwrite_local = False
config.set('DEFAULT', 'overwrite-local', overwrite_local and '1' or '0')
debian_config = config
return debian_config
class DebPackage:
def __init__(self, kind, name,
oldstyle=False, default_runtime=None,
pkgdir=None, pkgconfig=None, parse_versions=True):
self.kind = kind
self.name = name
self.version_field = None
self.oldstyle = oldstyle
self.parse_versions = parse_versions
self.default_runtime = default_runtime
self.shared_prefix = None
self.pkgdir = pkgdir
self.pkgconfig = pkgconfig
self.has_shared_extension = {}
self.has_private_extension = False
self.has_shared_module = {}
self.has_private_module = False
if pkgdir:
self.read_control()
else:
self.read_pyfiles()
#self.print_info()
def read_pyfiles(self):
self.shared_files = []
self.pylib_files = {}
self.other_pylib_files = []
self.private_files = []
self.omitted_files = []
self.pysupport_files = []
if self.pkgdir:
lines = []
for root, dirs, files in os.walk(self.pkgdir):
if root.endswith('DEBIAN'):
continue
if root != self.pkgdir:
d = root[len(self.pkgdir):]
lines.append(d)
for name in files:
lines.append(os.path.join(d, name))
else:
config_file = '/usr/share/pyshared-data/%s' % self.name
if self.pkgconfig:
lines = [fn for fn, t in self.pkgconfig.items('files')]
lines.sort()
elif os.path.isfile(config_file):
logging.debug("reading %s" % config_file)
self.pkgconfig = SafeConfigParser()
self.pkgconfig.optionxform = str
self.pkgconfig.readfp(file(config_file))
lines = [fn for fn, t in self.pkgconfig.items('files')]
lines.sort()
else:
lines = self.read_preinst_pkgconfig()
if lines:
pass
elif os.environ.has_key("PYCENTRAL_NO_DPKG_QUERY"):
logging.debug("Not using dpkg-query as requested")
lines = map(string.strip, open('/var/lib/dpkg/info/%s.list' % self.name).readlines())
else:
cmd = ['/usr/bin/dpkg-query', '-L', self.name]
try:
import subprocess
p = subprocess.Popen(cmd, bufsize=1,
shell=False, stdout=subprocess.PIPE)
fd = p.stdout
except ImportError:
fd = os.popen(' '.join(cmd))
lines = [s[:-1] for s in fd.readlines()]
if not self.pkgconfig:
pc = SafeConfigParser()
pc.optionxform = str
pc.add_section('python-package')
pc.set('python-package', 'format', '1')
pc.add_section('pycentral')
pc.set('pycentral', 'version', req_pycentral_version)
pc.add_section('files')
for line in lines:
if os.path.isdir(line) and not os.path.islink(line):
pc.set('files', line, 'd')
elif os.path.exists(line):
pc.set('files', line, 'f')
else:
pass # should not happen
self.pkgconfig = pc
old_shared_base = shared_base + self.name + '/site-packages/'
found_old_base = found_base2 = False
for line in lines:
fn = line
if fn.startswith(shared_base2):
# keep _all_ files and directories
self.shared_files.append(fn)
if fn.endswith('.py'):
self.has_shared_module['all'] = True
found_base2 = True
self.shared_prefix = shared_base2
continue
elif fn.startswith(old_shared_base):
# keep _all_ files and directories
self.shared_files.append(fn)
if fn.endswith('.py'):
self.has_shared_module['all'] = True
found_old_base = True
self.shared_prefix = old_shared_base
continue
elif fn.startswith('/usr/share/python-support') \
or fn.startswith('/usr/lib/python-support'):
self.pysupport_files.append(fn)
continue
elif not fn.endswith('.py') and not fn.endswith('.so'):
if re.match(r'/usr/lib/python\d\.\d/', fn):
self.other_pylib_files.append(fn)
continue
elif fn.startswith('/etc/') or fn.startswith('/usr/share/doc/'):
# omit files in /etc and /usr/share/doc
self.omitted_files.append(fn)
continue
elif re.search(r'/s?bin/', fn):
# omit files located in directories
self.omitted_files.append(fn)
continue
elif fn.startswith('/usr/lib/site-python/'):
version = pyversions.default_version(version_only=True)
self.pylib_files.setdefault(version, []).append(fn)
continue
elif re.match(r'/usr/lib/python\d\.\d/', fn):
version = fn[15:18]
if fn.endswith('.so'):
self.has_shared_extension[version] = True
if fn.endswith('.py'):
self.has_shared_module[version] = True
self.pylib_files.setdefault(version, []).append(fn)
continue
else:
self.private_files.append(fn)
if fn.endswith('.py'):
self.has_private_module = True
if found_old_base and found_base2:
raise PyCentralError, \
'shared files found in old (%s) and new locations (%s)' % (old_shared_base, shared_base2)
def read_preinst_pkgconfig(self):
try:
fd = open('/var/lib/dpkg/info/%s.preinst' % self.name, 'r')
except:
return None
buffer = None
for line in fd.readlines():
if line == '[python-package]\n':
buffer = cStringIO.StringIO()
if line in ('PYEOF\n', 'EOF\n'):
break
if buffer:
buffer.write(line)
if buffer is None:
return None
self.pkgconfig = SafeConfigParser()
self.pkgconfig.optionxform = str
self.pkgconfig.readfp(cStringIO.StringIO(buffer.getvalue()))
files = [fn for fn, t in self.pkgconfig.items('files')]
files.sort()
return files
def print_info(self, fd=sys.stdout):
fd.write('Package: %s\n' % self.name)
fd.write(' shared files :%4d\n' % len(self.shared_files))
fd.write(' private files :%4d\n' % len(self.private_files))
for ver, files in self.pylib_files.items():
fd.write(' pylib%s files:%4d\n' % (ver, len(files)))
def read_control(self):
"""read the debian/control file, extract the XS-Python-Version
field; check that XB-Python-Version exists for the package."""
if not os.path.exists('debian/control'):
raise PyCentralError, "debian/control not found"
self.version_field = None
self.sversion_field = None
try:
section = None
for line in file('debian/control'):
line = line.strip()
if line == '':
section = None
elif line.startswith('Source:'):
section = 'Source'
elif line.startswith('Package: ' + self.name):
section = self.name
elif line.startswith('XS-Python-Version:'):
if section != 'Source':
raise PyCentralError, \
'attribute XS-Python-Version not in Source section'
self.sversion_field = line.split(':', 1)[1].strip()
elif line.startswith('XB-Python-Version:'):
if section == self.name:
self.version_field = line.split(':', 1)[1].strip()
except:
pass
if self.version_field == None:
raise PyCentralVersionMissingError, \
'missing XB-Python-Version attribute in package %s' % self.name
if self.sversion_field == None:
raise PyCentralError, 'missing XS-Python-Version attribute'
if self.parse_versions:
self.sversion_info = parse_versions(self.sversion_field)
else:
self.sversion_info = 'all' # dummy
self.has_private_extension = self.sversion_info == 'current_ext'
def move_files(self):
"""move files from the installation directory to the pycentral location"""
import shutil
dsttop = self.pkgdir + shared_base2
try:
os.makedirs(dsttop)
except OSError:
pass
pversions = pyversions.supported_versions() \
+ pyversions.unsupported_versions() + pyversions.old_versions()
pversions = list(set(pversions))
# rename .egg-info files and dirs, remove *.py[co] files
rename = 'norename' not in os.environ.get('DH_PYCENTRAL', '')
vrx = re.compile(r'(.*)(-py\d\.\d)(.*)(\.egg-info|\.pth)$')
for pversion in pversions:
srctop = os.path.join(self.pkgdir, 'usr/lib', pversion, 'site-packages')
for root, dirs, files in os.walk(srctop, topdown=False):
for name in files:
m = vrx.match(name)
if m and rename:
name2 = ''.join(m.group(1, 3, 4))
print "rename: %s -> %s" % (name, name2)
os.rename(os.path.join(root, name), os.path.join (root, name2))
elif name.endswith('.pyc') or name.endswith('.pyo'):
os.unlink(os.path.join(root, name))
for name in dirs:
m = vrx.match(name)
if m and rename:
name2 = ''.join(m.group(1, 3, 4))
print "rename: %s -> %s" % (name, name2)
os.rename(os.path.join(root, name), os.path.join (root, name2))
# search for differences
import filecmp
class MyDircmp(filecmp.dircmp):
def report(self):
if self.left_only or self.right_only or self.diff_files or self.funny_files:
self.differs = True
print 'diff', self.left, self.right
if self.left_only:
self.left_only.sort()
print 'Only in', self.left, ':', self.left_only
if self.right_only:
self.right_only.sort()
print 'Only in', self.right, ':', self.right_only
if self.diff_files:
self.diff_files.sort()
print 'Differing files :', self.diff_files
if self.funny_files:
self.funny_files.sort()
print 'Trouble with common files :', self.funny_files
for pv1 in pversions:
for pv2 in pversions[pversions.index(pv1)+1:]:
site1 = os.path.join(self.pkgdir, 'usr/lib', pv1, 'site-packages')
site2 = os.path.join(self.pkgdir, 'usr/lib', pv2, 'site-packages')
if not (os.path.isdir(site1) and os.path.isdir(site2)):
continue
dco = MyDircmp(site1, site2)
dco.differs = False
dco.report_full_closure()
# move around
for pversion in pversions:
srctop = os.path.join(self.pkgdir, 'usr/lib', pversion, 'site-packages')
for root, dirs, files in os.walk(srctop):
if root == srctop:
d = '.'
else:
d = root[len(srctop)+1:]
for name in dirs:
src = os.path.join(root, name)
dst = os.path.join(dsttop, d, name)
try:
os.mkdir(dst)
shutil.copymode(src, dst)
except OSError:
pass
for name in files:
src = os.path.join(root, name)
dst = os.path.join(dsttop, d, name)
if re.search(r'\.so(\.\d+)*?$', name):
continue
# TODO: if dst already exists, make sure, src == dst
os.rename(src, dst)
# remove empty dirs in /usr/lib/pythonX.Y
for root, dirs, files in os.walk(self.pkgdir + '/usr/lib', topdown=False):
try:
if re.match("/usr/lib/python\d\.\d($|/)", root.replace(self.pkgdir, "")):
os.rmdir(root)
except OSError:
pass
try:
os.rmdir(self.pkgdir + '/usr/lib')
except OSError:
pass
# remove empty dirs in /usr/share/pyshared
for root, dirs, files in os.walk(self.pkgdir + shared_base2, topdown=False):
try:
os.rmdir(root)
except OSError:
pass
try:
os.rmdir(self.pkgdir + '/usr/share/pyshared')
except OSError:
pass
def gen_substvars(self):
supported = [d[6:] for d in pyversions.supported_versions()
if re.match(r'python\d\.\d', d)]
versions = ''
prversions = ''
self.depends = None
if len(self.has_shared_module) or len(self.has_shared_extension):
# shared modules / extensions
if len(self.has_shared_extension):
versions = self.has_shared_extension.keys()
else:
if self.sversion_info in ('current', 'current_ext'):
versions = 'current'
elif self.sversion_info == 'all':
versions = 'all'
prversions = supported
else:
versions = self.sversion_field
prversions = list(self.sversion_info.intersection(supported))
self.depends = version2depends(self.sversion_info)
elif self.has_private_module or self.has_private_extension:
if self.sversion_info == 'all':
versions = 'current'
elif self.sversion_info == 'current':
versions = 'current'
elif self.sversion_info == 'current_ext':
versions = [pyversions.default_version(version_only=True)]
elif isinstance(self.sversion_info, list) or isinstance(self.sversion_info, set):
# exact version info required, no enumeration, no relops
if len(self.sversion_info) != 1 or not re.match(r'\d\.\d', self.sversion_info[0]):
raise PyCentralError, 'no exact version for package with private modules'
versions = [list(self.sversion_info)[0]]
else:
raise PyCentralError, 'version error for package with private modules'
else:
# just "copy" it from the source field
if self.sversion_info == 'current':
versions = 'current'
elif self.sversion_info == 'current_ext':
versions = [pyversions.default_version(version_only=True)]
elif self.sversion_info == 'all':
versions = 'all'
prversions = supported
else:
versions = self.sversion_field
prversions = list(self.sversion_info.intersection(supported))
self.depends = version2depends(self.sversion_info)
if (len(self.has_shared_module) or len(self.has_shared_extension)) \
and self.has_private_module or self.has_private_extension:
# use case? use the information for the shared stuff
pass
if versions == '':
raise PyCentralError, 'unable to determine Python-Version attribute'
if isinstance(versions, list) or isinstance(versions, set):
self.version_field = ', '.join(versions)
else:
self.version_field = versions
if not self.depends:
self.depends = version2depends(versions)
if self.name.startswith('python-'):
if prversions == '':
prversions = versions
self.provides = ', '.join([self.name.replace('python-', 'python%s-' % ver)
for ver in prversions])
def set_version_field(self, version_field):
self.version_field = version_field
if self.parse_versions:
self.version_info = pyversions.parse_versions(version_field)
def read_version_info(self):
"""Read the Python-Version information field"""
if self.version_field:
return
if self.pkgconfig and self.pkgconfig.has_option('python-package', 'python-version'):
self.version_field = self.pkgconfig.get('python-package', 'python-version')
logging.debug("Using python-version from pkgconfig: %s" % self.version_field)
elif os.environ.has_key("PYCENTRAL_NO_DPKG_QUERY"):
logging.debug("Not using dpkg-query as requested")
needle = "Package: %s\n" % self.name
for block in open("/var/lib/dpkg/status").read().split("\n\n"):
if needle in block:
for line in block.split("\n"):
if line.startswith('Python-Version:'):
self.version_field = line.split(':', 1)[1].strip()
break
else:
logging.debug("dpkg-query -s %s" % self.name)
cmd = ['/usr/bin/dpkg-query', '-s', self.name]
try:
import subprocess
p = subprocess.Popen(cmd, bufsize=1,
shell=False, stdout=subprocess.PIPE)
fd = p.stdout
except ImportError:
fd = os.popen(' '.join(cmd))
for line in fd:
if line.startswith('Python-Version:'):
self.version_field = line.split(':', 1)[1].strip()
break
fd.close()
# now verify/parse it
if not self.version_field:
raise PyCentralError, "package has no field Python-Version"
if self.parse_versions:
self.version_info = pyversions.parse_versions(self.version_field)
def set_default_runtime_from_version_info(self):
versions = list(pyversions.requested_versions(self.version_field, version_only=True))
if not versions:
#raise PyCentralError, "no matching runtime for `%s'" % self.version_field
logging.warn("%s: no matching runtime for `%s', using default"
% (self.name, self.version_field))
self.default_runtime = get_default_runtime()
if len(versions) == 1:
self.default_runtime = get_runtime_for_version(versions[0])
elif pyversions.default_version(version_only=True) in versions:
self.default_runtime = get_default_runtime()
else:
self.default_runtime = get_runtime_for_version(versions[0])
def byte_compile(self, runtimes, bc_option, exclude_regex, ignore_errors=False):
"""byte compiles all files not handled by pycentral"""
logging.debug(" byte-compile %s" % self.name)
if self.shared_files:
ppos = len(self.shared_prefix)
for rt in runtimes:
linked_files = [ rt.prefix + fn[ppos:]
for fn in self.shared_files
if fn[-3:] == '.py']
rt.byte_compile(linked_files, bc_option, exclude_regex, ignore_errors)
for pyver, files in self.pylib_files.items():
logging.debug("bc for v%s (%d files)" % (pyver, len(files)))
rt = get_runtime_for_version(pyver)
if rt in runtimes:
rt.byte_compile(files, bc_option, exclude_regex)
if self.private_files:
logging.debug("bc %s private (%d files)" %
(self.default_runtime.version, len(self.private_files)))
rt = self.default_runtime
rt.byte_compile(self.private_files, bc_option, exclude_regex)
def remove_bytecode(self):
"""remove all byte-compiled files not handled by pycentral"""
assert self.oldstyle
logging.debug(" remove byte-code for %s" % self.name)
pyfiles = []
for files in self.pylib_files.values():
pyfiles.extend(files)
pyfiles.extend(self.private_files)
errors = False
for ext in ('c', 'o'):
for fn in pyfiles:
fnc = fn + ext
if os.path.exists(fnc):
try:
os.unlink(fnc)
except OSError, e:
print "Sorry", e
errors = True
if errors:
raise PyCentralError
def link_shared_files(self, rt):
#if samefs(rt.prefix, self.shared_files[0]):
# link_cmd = os.link
#else:
# link_cmd = os.symlink
logging.debug("\tlink shared files %s/%s" % (rt.name, self.name))
if not self.shared_files:
return []
link_cmd = os.symlink
ppos = len(self.shared_prefix)
existing_files = []
for fn in self.shared_files:
fn2 = rt.prefix + fn[ppos:]
if os.path.isdir(fn) and not os.path.islink(fn):
continue
if os.path.exists(fn2):
link = abs_link = None
if os.path.islink(fn2):
link = abs_link = os.readlink(fn2)
if link.startswith('../'):
abs_link = os.path.normpath(os.path.join(os.path.dirname(fn2), link))
if abs_link == fn or link == fn:
continue
if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
existing_files.append(fn2)
if existing_files:
conf = get_debian_config()
overwrite_local = conf.get('DEFAULT', 'overwrite-local') == '1'
if overwrite_local:
print "overwriting local files"
linked_files = []
try:
for fn in self.shared_files:
fn2 = rt.prefix + fn[ppos:]
if os.path.isdir(fn) and not os.path.islink(fn):
if os.path.isdir(fn2):
continue
os.makedirs(fn2)
linked_files.append(fn2)
else:
if os.path.exists(fn2):
msg = "already exists: %s" % fn2
link = abs_link = None
if os.path.islink(fn2):
link = abs_link = os.readlink(fn2)
if link.startswith('../'):
abs_link = os.path.normpath(os.path.join(os.path.dirname(fn2), link))
if abs_link == fn or link == fn:
linked_files.append(fn2)
continue
if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
msg = msg + " -> %s" % link
if overwrite_local:
print "warning:", msg
os.unlink(fn2)
else:
continue # raise PyCentralError, msg at end of method
# make sure that fn2 really does not exist; this is a
# special hack to make pycentral work with fakechroot,
# which has a slightly weird treatment of symlinks
# now needed to switch between old and new prefix
try:
os.unlink(fn2)
except OSError:
pass
link_cmd(fn, fn2)
linked_files.append(fn2)
except PyCentralError, msg:
raise
except Exception, msg:
print msg
# FIXME: undo
linked_files.reverse()
return []
else:
if existing_files and not overwrite_local:
raise PyCentralError, "not overwriting local files"
return linked_files
def unlink_shared_files(self, rt):
logging.debug('\tunlink_shared_files %s/%s' % (rt.name, self.name))
if not self.shared_files:
return
ppos = len(self.shared_prefix)
shared_files = self.shared_files[:]
shared_files.reverse()
for fn in shared_files:
fn2 = rt.prefix + fn[ppos:]
if os.path.isdir(fn2) and not os.path.islink(fn2):
try:
os.removedirs(fn2)
except OSError:
pass
else:
if os.path.exists(fn2):
os.unlink(fn2)
def install(self, runtimes, bc_option, exclude_regex,
byte_compile_default=True, ignore_errors=False):
logging.debug('\tinstall package %s' % self.name)
# install shared .py files
if self.shared_files:
for rt in runtimes:
linked_files = self.link_shared_files(rt)
rt.byte_compile(linked_files, bc_option, exclude_regex, ignore_errors)
# byte compile files inside prefix
if self.pylib_files:
for pyver, files in self.pylib_files.items():
rt = get_runtime_for_version(pyver)
if rt in runtimes:
rt.byte_compile(files, bc_option, exclude_regex, ignore_errors)
# byte compile with the default runtime for the package
if byte_compile_default:
if self.private_files:
self.default_runtime.byte_compile(self.private_files, bc_option,
exclude_regex, ignore_errors)
def prepare(self, runtimes, old_runtimes, old_pkg, ignore_errors=False):
logging.debug('\tprepare package %s' % self.name)
if old_pkg and old_pkg.private_files:
fs_in_old = set([fn for fn in old_pkg.private_files if fn[-3:] == '.py'])
fs_in_new = set([fn for fn in self.private_files if fn[-3:] == '.py'])
removed_fs = list(fs_in_old.difference(fs_in_new))
if removed_fs:
logging.debug_list('\t', 'removed private', removed_fs)
default_runtime.remove_byte_code(removed_fs)
old_pylib_fs = []
if old_pkg and old_pkg.pylib_files:
for pyver, files in old_pkg.pylib_files.items():
fs_in_old = set([fn for fn in files if fn[-3:] == '.py'])
fs_in_new = set([fn for fn in self.pylib_files.get(pyver, []) if fn[-3:] == '.py'])
removed_fs = list(fs_in_old.difference(fs_in_new))
if removed_fs:
logging.debug_list('\t', 'removed pylib', removed_fs)
default_runtime.remove_byte_code(removed_fs)
old_pylib_fs += files
old_pylib_fs += old_pkg.other_pylib_files
if old_pkg and old_pkg.shared_files:
for rt in old_runtimes:
if rt in runtimes:
continue
linked_files = [ rt.prefix + fn[ppos:]
for fn in old_pkg.shared_files if fn[-3:] == '.py']
if linked_files:
logging.debug_list('\t', 'removed runtimes', linked_files)
default_runtime.remove_byte_code(linked_files)
self.unlink_files(rt)
if not self.shared_files:
return
dirs_in_new = set([fn for fn, t in self.pkgconfig.items('files')
if t == 'd' if fn.startswith(self.shared_prefix)])
dirs_in_old = set()
if old_pkg:
dirs_in_old = set([fn for fn, t in old_pkg.pkgconfig.items('files')
if t == 'd' if fn.startswith(self.shared_prefix)])
new_dirs = list(dirs_in_new.difference(dirs_in_old))
new_dirs.sort()
removed_dirs = list(dirs_in_old.difference(dirs_in_new))
removed_dirs.sort()
fs_in_new = set([fn for fn, t in self.pkgconfig.items('files')
if t == 'f' if fn.startswith(self.shared_prefix)])
fs_in_old = set()
if old_pkg:
fs_in_old = set([fn for fn, t in old_pkg.pkgconfig.items('files')
if t == 'f' if fn.startswith(self.shared_prefix)])
new_fs = list(fs_in_new.difference(fs_in_old))
new_fs.sort()
removed_fs = list(fs_in_old.difference(fs_in_new))
removed_fs.sort()
logging.debug_list('\t', 'new dirs', new_dirs)
logging.debug_list('\t', 'removed dirs', removed_dirs)
logging.debug_list('\t', 'new files', new_fs)
logging.debug_list('\t', 'removed files', removed_fs)
link_cmd = os.symlink
ppos = len(self.shared_prefix)
existing_files = []
for rt in runtimes:
for f1 in new_fs:
f2 = rt.prefix + f1[ppos:]
if os.path.exists(f2):
link = abs_link = None
if os.path.islink(f2):
link = abs_link = os.readlink(f2)
if link.startswith('../'):
abs_link = os.path.normpath(os.path.join(os.path.dirname(f2), link))
if abs_link == f1 or link == f1:
continue
if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
existing_files.append(f2)
if existing_files:
# if the current installed version does not use python-central
# then having a file here is expected and harmless
if not self.name in [p for (p,v) in read_dpkg_status()]:
logging.info("%s: upgrade from package version not using python-central" % self.name)
return
# if all existing files are found in the old package in
# /usr/lib/pythonX.Y/site-packages, and moved to the shared area,
# do nothing.
not_in_same_pkg = set(existing_files)
if old_pkg:
not_in_same_pkg.difference_update(old_pylib_fs)
if not_in_same_pkg == set():
logging.info("%s: upgrade from package version with unmoved files" % self.name)
return
conf = get_debian_config()
overwrite_local = conf.get('DEFAULT', 'overwrite-local') == '1'
if overwrite_local:
print "overwriting local files"
for rt in runtimes:
for d1 in new_dirs:
d2 = rt.prefix + d1[ppos:]
if os.path.isdir(d2):
continue
os.makedirs(d2)
for f1 in new_fs:
f2 = rt.prefix + f1[ppos:]
if os.path.exists(f2):
msg = "already exists: %s" % f2
link = abs_link = None
if os.path.islink(f2):
link = abs_link = os.readlink(f2)
if link.startswith('../'):
abs_link = os.path.normpath(os.path.join(os.path.dirname(f2), link))
if abs_link == f1 or link == f1:
continue
if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
msg = msg + " -> %s" % link
if overwrite_local:
print "warning:", msg
os.unlink(f2)
else:
continue # raise PyCentralError, msg at end of loop
# hack to make pycentral work with fakechroot
# now needed to switch between old and new prefix
try:
os.unlink(f2)
except OSError:
pass
try:
d = os.path.dirname(f2)
if not os.path.isdir(d):
print "create directory %s" % d
os.makedirs(d)
link_cmd(f1, f2)
except OSError:
print "unable to create symlink %s" % f2
raise
if existing_files and not overwrite_local:
raise PyCentralError, "not overwriting local files"
for rt in runtimes:
for f1 in removed_fs:
f2 = rt.prefix + f1[ppos:]
try:
os.unlink(f2)
os.unlink(f2 + 'c')
os.unlink(f2 + 'o')
except OSError:
pass
for d1 in removed_dirs:
d2 = rt.prefix + d1[ppos:]
try:
os.rmdir(d2)
except OSError:
pass
return
def remove(self, runtimes, remove_script_files=True):
logging.debug('\tremove package %s' % self.name)
# remove shared .py files
if self.shared_files:
ppos = len(self.shared_prefix)
for rt in runtimes:
linked_files = [ rt.prefix + fn[ppos:]
for fn in self.shared_files
if fn[-3:] == '.py']
#print self.shared_files
#print linked_files
default_runtime.remove_byte_code(linked_files)
self.unlink_shared_files(rt)
# remove byte compiled files inside prefix
if self.pylib_files:
for pyver, files in self.pylib_files.items():
rt = get_runtime_for_version(pyver)
if rt in runtimes:
default_runtime.remove_byte_code(files)
# remove byte code for script files
if remove_script_files:
if self.private_files:
default_runtime.remove_byte_code(self.private_files)
def update_bytecode_files(self, runtimes, rt_default, bc_option):
# byte-compile with default python version
logging.debug('\tupdate byte-code for %s' % self.name)
exclude_regex = None
# update shared .py files
if self.shared_files:
ppos = len(self.shared_prefix)
for rt in runtimes:
if rt == rt_default:
linked_files = self.link_shared_files(rt)
rt.byte_compile(linked_files, bc_option, exclude_regex)
else:
linked_files = [ rt.prefix + fn[ppos:]
for fn in self.shared_files
if fn[-3:] == '.py']
rt.remove_byte_code(linked_files)
self.unlink_shared_files(rt)
# byte compile with the default runtime for the package
if self.private_files:
self.default_runtime.byte_compile(self.private_files,
bc_option, exclude_regex, force=True)
known_actions = {}
def register_action(action_class):
known_actions[action_class.name] = action_class
class Action:
_option_parser = None
name = None
help = ""
usage = "<options>"
def __init__(self):
self.errors_occured = 0
parser = self.get_option_parser()
parser.set_usage(
'usage: %s [<options> ...] %s %s' % (program, self.name, self.usage))
def get_option_parser(self):
if not self._option_parser:
p = OptionParser()
self._option_parser = p
return self._option_parser
def info(self, msg, stream=sys.stderr):
logging.info('%s %s: %s' % (program, self.name, msg))
def warn(self, msg, stream=sys.stderr):
logging.warn('%s %s: %s' % (program, self.name, msg))
def error(self, msg, stream=sys.stderr, go_on=False):
logging.error('%s %s: %s' % (program, self.name, msg))
self.errors_occured += 1
if not go_on:
sys.exit(1)
def parse_args(self, arguments):
self.options, self.args = self._option_parser.parse_args(arguments)
return self.options, self.args
def check_args(self, global_options):
return self.errors_occured
def run(self, global_opts):
pass
class ActionByteCompile(Action):
"""byte compile the *.py files in <package> using the the
default python version (or use the version specified with -v.
Any additional directory arguments are ignored (only files
found in the package are byte compiled. Files in
/usr/lib/pythonX.Y are compiled with the matching python version.
bccompile is a replacement for the current byte compilation
generated by the dh_python debhelper script.
"""
name = 'bccompile'
help = 'byte compile .py files in a package'
usage = '[<options>] <package> [<dir> ...]'
def get_option_parser(self):
if not self._option_parser:
p = OptionParser()
p.add_option('-x', '--exclude',
help="skip files matching the regular expression",
default=None, action='store', dest='exclude')
p.add_option('-V', '--version',
help="byte compile using this python version",
default='current', action='store', dest='version')
self._option_parser = p
return self._option_parser
def check_args(self, global_options):
if len(self.args) < 1:
self._option_parser.print_help()
sys.exit(1)
self.pkgname = self.args[0]
self.runtime = get_runtime_for_version(self.options.version)
if not self.runtime:
self.error("unknown runtime version %s" % self.options.version)
if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
self.error("package %s is not installed" % self.pkgname)
self.pkg = DebPackage('package', self.pkgname, oldstyle=False,
default_runtime=self.runtime)
self.pkg.read_version_info()
return self.errors_occured
def run(self, global_options):
logging.debug('bccompile %s' % self.pkgname)
runtimes = get_installed_runtimes()
config = get_debian_config()
bc_option = config.get('DEFAULT', 'byte-compile')
requested = pyversions.requested_versions_for_runtime(self.pkg.version_field, version_only=True)
used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
# called with directories as arguments
if 0 and self.directories:
try:
for version, dirs in self.pylib_dirs.items():
rt = get_runtime_for_version(version)
rt.byte_compile_dirs(dirs, bc_option, self.options.exclude)
if self.private_dirs:
version = pkg.version_field
if version == 'current':
version = pyversions.default_version(version_only=True)
rt = get_runtime_for_version(version)
rt.byte_compile_dirs(private_dirs, bc_option, self.options.exclude)
except PyCentralError:
self.error("error byte-compiling package `%s'" % self.pkgname)
return
try:
self.pkg.byte_compile(used_runtimes, bc_option, self.options.exclude)
except PyCentralError:
self.error("error byte-compiling package `%s'" % self.pkgname)
register_action(ActionByteCompile)
class ActionPkgInstall(Action):
name = 'pkginstall'
help = 'make a package available for all supported runtimes'
usage = '[<options>] <package>'
def get_option_parser(self):
if not self._option_parser:
p = OptionParser()
p.add_option('-x', '--exclude',
help="skip files matching the regular expression",
default=None, action='store', dest='exclude')
self._option_parser = p
return self._option_parser
def check_args(self, global_options):
if len(self.args) != 1:
self._option_parser.print_help()
sys.exit(1)
self.pkgname = self.args[0]
if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
self.error("package %s is not installed" % self.pkgname)
return self.errors_occured
def run(self, global_options):
runtimes = get_installed_runtimes()
config = get_debian_config()
bc_option = config.get('DEFAULT', 'byte-compile')
pkg = DebPackage('package', self.args[0], oldstyle=False)
pkg.read_version_info()
requested = pyversions.requested_versions_for_runtime(pkg.version_field, version_only=True)
used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
try:
pkg.set_default_runtime_from_version_info()
except ValueError:
# Package doesn't provide support for any supported runtime
if len(used_runtimes) == 0:
self.error('%s needs unavailable runtime (%s)'
% (self.pkgname, pkg.version_field))
else:
# Still byte compile for the available runtimes (with the
# first matching runtime)
pkg.default_runtime = get_runtime_for_version(used_runtimes[0])
logging.debug('\tavail=%s, pkg=%s, install=%s'
% ([rt.short_name for rt in runtimes],
pkg.version_field,
[rt.short_name for rt in used_runtimes]))
try:
pkg.install(used_runtimes, bc_option,
self.options.exclude, byte_compile_default=True)
except PyCentralError, msg:
self.error(msg)
register_action(ActionPkgInstall)
class ActionPkgPrepare(Action):
name = 'pkgprepare'
help = 'prepare a package for all supported runtimes'
usage = '[<options>] <package>'
def get_option_parser(self):
if not self._option_parser:
p = OptionParser()
p.add_option('-x', '--exclude',
help="skip files matching the regular expression",
default=None, action='store', dest='exclude')
self._option_parser = p
return self._option_parser
def check_args(self, global_options):
if len(self.args) != 1:
self._option_parser.print_help()
sys.exit(1)
self.pkgname = self.args[0]
# FIXME: run from the preinst, package may not exist
#if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
# self.error("package %s is not installed" % self.pkgname)
return self.errors_occured
def run(self, global_options):
runtimes = get_installed_runtimes()
config = get_debian_config()
pkgconfig = SafeConfigParser()
pkgconfig.optionxform = str
pkgconfig.readfp(sys.stdin)
version_field = pkgconfig.get('python-package', 'python-version')
try:
requested = pyversions.requested_versions_for_runtime(version_field, version_only=True)
except pyversions.PyCentralEmptyValueError, msg:
# cannot install yet; remove the symlinked files and byte code files from the old
# version, rely on the pkginstall in the postinst.
print "pycentral: required runtimes not yet installed, skip pkgprepare, call pkgremove"
runtimes = get_installed_runtimes(with_unsupported=True)
pkg = DebPackage('package', self.args[0], oldstyle=False)
pkg.read_version_info()
pkg.default_runtime = get_default_runtime()
try:
pkg.remove(runtimes, remove_script_files=True)
except PyCentralError, msg:
self.warn(msg)
return
used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
pkg = DebPackage('package', self.args[0], oldstyle=False, pkgconfig=pkgconfig)
pkg.set_version_field(version_field)
try:
pkg.set_default_runtime_from_version_info()
except ValueError:
# Package doesn't provide support for any supported runtime
if len(used_runtimes) == 0:
self.error('%s needs unavailable runtime (%s)'
% (self.pkgname, pkg.version_field))
else:
# Still byte compile for the available runtimes (with the
# first matching runtime)
pkg.default_runtime = get_runtime_for_version(used_runtimes[0])
if os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
old_pkg = DebPackage('package', self.args[0], oldstyle=False)
try:
old_pkg.read_version_info()
except PyCentralError:
old_pkg.set_version_field(version_field)
old_requested = pyversions.requested_versions_for_runtime(old_pkg.version_field, version_only=True)
old_used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
else:
old_pkg = None
old_used_runtimes = []
logging.debug('\tavail=%s, pkg=%s, prepare=%s'
% ([rt.short_name for rt in runtimes],
version_field,
[rt.short_name for rt in used_runtimes]))
try:
pkg.prepare(used_runtimes, old_used_runtimes, old_pkg)
except PyCentralError, msg:
self.error(msg)
register_action(ActionPkgPrepare)
class ActionBCRemove(Action):
"""remove the byte-compiled files in <package>.
bccompile is a replacement for the current byte compilation
generated by the dh_python debhelper script.
"""
name = 'bcremove'
help = 'remove the byte compiled .py files'
usage = '<package>'
def check_args(self, global_options):
if len(self.args) != 1:
self._option_parser.print_help()
sys.exit(1)
self.pkgname = self.args[0]
if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
self.error("package %s is not installed" % self.pkgname)
return self.errors_occured
def run(self, global_options):
pkg = DebPackage('package', self.args[0], oldstyle=True)
try:
pkg.remove_bytecode()
except PyCentralError, msg:
self.error(msg)
register_action(ActionBCRemove)
class ActionPkgRemove(Action):
"""
"""
name = 'pkgremove'
help = 'remove a package installed for all supported runtimes'
usage = '<package>'
def check_args(self, global_options):
if len(self.args) != 1:
self._option_parser.print_help()
sys.exit(1)
self.pkgname = self.args[0]
if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
self.error("package %s is not installed" % self.pkgname)
return self.errors_occured
def run(self, global_options):
runtimes = get_installed_runtimes(with_unsupported=True)
pkg = DebPackage('package', self.args[0], oldstyle=False)
pkg.read_version_info()
try:
pkg.set_default_runtime_from_version_info()
except ValueError:
# original runtime is already removed, use the default for removal
pkg.default_runtime = get_default_runtime()
try:
pkg.remove(runtimes, remove_script_files=True)
except PyCentralError, msg:
self.error(msg)
register_action(ActionPkgRemove)
class ActionRuntimeInstall(Action):
name = 'rtinstall'
help = 'make installed packages available for this runtime'
def get_option_parser(self):
if not self._option_parser:
p = OptionParser()
p.add_option('-i', '--ignore-errors',
help="ignore errors during byte compilations",
default=None, action='store', dest='ignore')
self._option_parser = p
return self._option_parser
def check_args(self, global_options):
if len(self.args) != 1:
self._option_parser.print_help()
sys.exit(1)
self.rtname = self.args[0]
if self.rtname[-8:] == '-minimal':
self.rtname = self.rtname[:-8]
self.runtime = None
for rt in get_installed_runtimes():
if rt.name == self.rtname:
self.runtime = rt
break
if not self.runtime:
self.error('installed runtime %s not found' % self.rtname)
return self.errors_occured
def run(self, global_options):
packages = [(p, v) for p, v in read_dpkg_status()
if not p in (self.rtname, self.rtname+'-minimal')]
needed_packages = []
for pkgname, vstring in packages:
try:
requested = list(pyversions.requested_versions(vstring, version_only=True))
except ValueError:
logging.info('\tunsupported for %s: %s (%s)' % (self.rtname, pkgname, vstring))
continue
if self.runtime.short_name in requested:
needed_packages.append((pkgname, vstring, requested))
logging.info('\t%d packages with Python-Version info installed, %d for %s'
% (len(packages), len(needed_packages), self.rtname))
# XXX not needed for an upgrade of a runtime
byte_compile_for_default = (self.runtime == default_runtime)
bc_option = get_debian_config().get('DEFAULT', 'byte-compile')
for pkgname, vstring, vinfo in needed_packages:
try:
logging.info('\tsupport %s for %s' % (pkgname, self.rtname))
pkg = DebPackage('package', pkgname, oldstyle=False)
pkg.read_version_info()
try:
pkg.set_default_runtime_from_version_info()
except ValueError:
logging.warn('\t%s not available for %s (%s)'
% (pkgname, self.rtname, pkg.version_field))
pkg.install([self.runtime], bc_option, None,
byte_compile_for_default,
ignore_errors = self.options.ignore != None)
except PyCentralError, msg:
self.error('package %s: %s' % (pkgname, msg))
register_action(ActionRuntimeInstall)
class ActionRuntimeRemove(Action):
name = 'rtremove'
help = 'remove packages installed for this runtime'
def check_args(self, global_options):
if len(self.args) != 1:
self._option_parser.print_help()
sys.exit(1)
self.rtname = self.args[0]
if self.rtname[-8:] == '-minimal':
self.rtname = self.rtname[:-8]
self.runtime = None
for rt in get_installed_runtimes(with_unsupported=True):
if rt.name == self.rtname:
self.runtime = rt
break
if not self.runtime:
self.error('installed runtime %s not found' % self.rtname)
return self.errors_occured
def run(self, global_options):
packages = [(p, v) for p, v in read_dpkg_status(verbose=True)
if not p in (self.rtname, self.rtname+'-minimal')]
needed_packages = []
import subprocess
for pkgname, vstring in packages:
if not os.path.exists('/var/lib/dpkg/info/%s.list' % pkgname):
# already removed, but /var/lib/dpkg/status not yet updated
continue
cmd = ['/usr/bin/dpkg-query', '-W', '-f', '${Status}\n', pkgname]
p = subprocess.Popen(cmd, bufsize=1,
shell=False, stdout=subprocess.PIPE)
fd = p.stdout
status = fd.readline().strip().split()
fd.close()
if not 'installed' in status:
# already removed, but /var/lib/dpkg/status not yet updated
continue
try:
requested = list(pyversions.requested_versions_for_runtime(vstring, version_only=True))
except ValueError:
logging.info('\tunsupported for %s: %s (%s)' % (self.rtname, pkgname, vstring))
continue
if self.runtime.short_name in requested:
needed_packages.append((pkgname, vstring, requested))
logging.info('\t%d pycentral supported packages installed, %d for %s'
% (len(packages), len(needed_packages), self.rtname))
failed = []
for pkgname, vstring, vinfo in needed_packages:
logging.info('\trtremove: remove package %s for %s' % (pkgname, self.rtname))
pkg = DebPackage('package', pkgname)
pkg.set_version_field(vstring)
try:
pkg.set_default_runtime_from_version_info()
except ValueError:
# original runtime is already removed, use the default for removal
pkg.default_runtime = get_default_runtime()
try:
pkg.remove([self.runtime], remove_script_files=False)
except PyCentralError, msg:
self.error('failed to remove %s support for package %s' % (self.rtname, pkgname), go_on=True)
failed.append(pkgname)
if failed:
self.error('failed to remove %s support for %d packages' % len(failed))
register_action(ActionRuntimeRemove)
class ActionUpdateDefault(Action):
name = 'updatedefault'
help = 'update the default python version'
usage = '<old runtime> <new runtime>'
def check_args(self, global_options):
if len(self.args) != 2:
self._option_parser.print_help()
sys.exit(1)
self.oldrtname = self.args[0]
self.rtname = self.args[1]
packages = read_dpkg_status()
self.needed_packages = []
for pkgname, vstring in packages:
if vstring.find('current') == -1:
continue
try:
versions = pyversions.requested_versions(vstring, version_only=True)
except ValueError:
self.error("package %s is not ready to be updated for %s"
% (pkgname, self.rtname))
continue
pkg = DebPackage('package', pkgname)
self.needed_packages.append(pkg)
return self.errors_occured
def run(self, global_options):
logging.info('\tupdate default: update %d packages for %s'
% (len(self.needed_packages), self.rtname))
runtimes = get_installed_runtimes()
default_rt = get_default_runtime()
bc_option = get_debian_config().get('DEFAULT', 'byte-compile')
try:
for pkg in self.needed_packages:
pkg.read_version_info()
pkg.set_default_runtime_from_version_info()
if pkg.shared_files or pkg.private_files:
pkg.update_bytecode_files(runtimes, default_rt, bc_option)
except PyCentralError, msg:
self.error(msg)
register_action(ActionUpdateDefault)
class ActionShowDefault(Action):
name = 'showdefault'
help = 'Show default python version number'
def check_args(self, global_options):
if len(self.args) != 0:
self._option_parser.print_help()
sys.exit(1)
return self.errors_occured
def run(self, global_options):
print pyversions.default_version(version_only=True)
sys.stderr.write("pycentral showdefault is deprecated, use `pyversions -vd'\n")
register_action(ActionShowDefault)
class ActionShowVersions(Action):
name = 'showversions'
help = 'Show version numbers of supported python versions'
def check_args(self, global_options):
if len(self.args) != 0:
self._option_parser.print_help()
sys.exit(1)
return self.errors_occured
def run(self, global_options):
supported = pyversions.supported_versions()
versions = [d[6:] for d in supported if re.match(r'python\d\.\d', d)]
print ' '.join(versions)
sys.stderr.write("pycentral showversions is deprecated, use `pyversions -vs'\n")
register_action(ActionShowVersions)
class ActionShowSupported(Action):
name = 'showsupported'
help = 'Show the supported python versions'
def check_args(self, global_options):
if len(self.args) != 0:
self._option_parser.print_help()
sys.exit(1)
return self.errors_occured
def run(self, global_options):
supported = pyversions.supported_versions()
print ' '.join(supported)
sys.stderr.write("pycentral showsupported is deprecated, use `pyversions -s'\n")
register_action(ActionShowSupported)
class ActionPyCentralDir(Action):
name = 'pycentraldir'
help = 'Show the pycentral installation directory for the package'
usage = '<package>'
def check_args(self, global_options):
if len(self.args) != 1:
self._option_parser.print_help()
sys.exit(1)
self.pkgname = self.args[0]
return self.errors_occured
def run(self, global_options):
if shared_base2[-1] == '/':
print shared_base2[:-1]
else:
print shared_base2
register_action(ActionPyCentralDir)
class ActionVersion(Action):
name = 'version'
help = 'Show the pycentral version'
def check_args(self, global_options):
if len(self.args) != 0:
self._option_parser.print_help()
sys.exit(1)
return self.errors_occured
def run(self, global_options):
sys.stdout.write("%s\n" % pycentral_version)
register_action(ActionVersion)
class ActionDebhelper(Action):
name = 'debhelper'
help = 'move files to pycentral location, variable substitutions'
usage = '[-p|--provides] [--no-move] <package> [<package directory>]'
def get_option_parser(self):
if not self._option_parser:
envvar = os.environ.get('DH_PYCENTRAL', '')
substvars_default = 'no'
if 'substvars=file' in envvar:
substvars_default = 'file'
if 'substvars=stdout' in envvar:
substvars_default = 'stdout'
p = OptionParser()
p.add_option('-p', '--provides',
help="generate substitution for python:Provides",
default='add-provides' in envvar, action='store_true', dest='provides')
p.add_option('--no-move', '--nomove',
help="do not move files to pycentral location",
default='no-move' in envvar or 'nomove' in envvar, action='store_true', dest='nomove')
p.add_option('--stdout',
help="just print substitution variables to stdout",
default='stdout' in envvar, action='store_true', dest='stdout')
p.add_option('--substvars',
help="where to print substitution vars (no, file, stdout)",
default=substvars_default, dest='substvars')
p.add_option('--no-act', '--dry-run',
help="dry run",
default=('dry-run' in envvar) or ('no-act' in envvar),
action='store_true', dest='dryrun')
self._option_parser = p
return self._option_parser
def check_args(self, global_options):
if not len(self.args) in (1, 2):
self._option_parser.print_help()
sys.exit(1)
if 'file' in self.options.substvars:
self.options.substvars = 'file'
if 'stdout' in self.options.substvars:
self.options.substvars = 'stdout'
return self.errors_occured
def run(self, global_options):
if len(self.args) < 2:
pkgdir = 'debian/' + self.args[0]
else:
pkgdir = self.args[1]
try:
pkg = DebPackage('package', self.args[0], pkgdir=pkgdir,
parse_versions=self.options.substvars!='no')
if not self.options.nomove:
pkg.move_files()
pkg.read_pyfiles()
if self.options.substvars!='no':
pkg.gen_substvars()
except PyCentralVersionMissingError, msg:
self.warn(msg)
return
except PyCentralError, msg:
self.error(msg)
out = None
if self.options.stdout or self.options.substvars == 'stdout':
out = sys.stdout
elif self.options.substvars == 'file':
out = file('debian/%s.substvars' % pkg.name, 'a+')
if out:
out.write('python:Versions=%s\n' % pkg.version_field)
out.write('python:Depends=%s\n' % pkg.depends)
out.write('python:Provides=%s\n' % pkg.provides)
register_action(ActionDebhelper)
# match a string with the list of available actions
def action_matches(action, actions):
prog = re.compile('[^-]*?-'.join(action.split('-')))
return [a for a in actions if prog.match(a)]
def usage(stream, msg=None):
print >>stream, msg
print >>stream, "use `%s help' for help on actions and arguments" % program
print >>stream
sys.exit(1)
# parse command line arguments
def parse_options(args):
shortusage = 'usage: %s [<option> ...] <action> <pkgname>' % program
parser = OptionParser(usage=shortusage)
parser.disable_interspersed_args()
# setup the parsers object
parser.remove_option('-h')
parser.add_option('-h', '--help',
help='help screen',
action='store_true', dest='help')
parser.add_option('-v', '--verbose',
help='verbose mode',
action='store_true', dest='verbose')
global_options, args = parser.parse_args()
# Print the help screen and exit
if len(args) == 0 or global_options.help:
parser.print_help()
print "\nactions:"
action_names = known_actions.keys()
action_names.sort()
for n in action_names:
print " %-21s %s" % (n, known_actions[n].help)
print ""
sys.exit(1)
# check if the specified action really exists
action_name = args[0]
del args[0]
matching_actions = action_matches(action_name, known_actions.keys())
if len(matching_actions) == 0:
usage(sys.stderr, "unknown action `%s'" % action_name)
elif len(matching_actions) > 1:
usage(sys.stderr,
"ambiguous action `%s', matching actions: %s"
% (action_name, strlist(matching_actions)))
else:
action_name = matching_actions[0]
# instantiate an object for the action and parse the remaining arguments
action = known_actions[action_name]()
action_options, action_names = action.parse_args(args)
return global_options, action
class Logging:
DEBUG, INFO, WARN, ERROR = range(4)
def __init__(self, level=WARN):
self.fd = None
self.level = level
try:
self.fd = file('/var/log/pycentral.log', 'a+')
except IOError:
self.fd = None
def msg(self, level, s):
if level < self.level:
return
d = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
if self.fd:
self.fd.write('%s %s %s\n' % (d, level, s))
self.fd.flush()
sys.stdout.write('pycentral: %s\n' % (s))
sys.stdout.flush()
def info(self, s):
self.msg(self.INFO, s)
def warn(self, s):
self.msg(self.WARN, s)
def error(self, s):
self.msg(self.ERROR, s)
sys.stderr.write('%s\n' % s)
def debug(self, s):
self.msg(self.DEBUG, s)
def debug_list(self, tab, s, l, n=4):
l2 = l[:min(n, len(l))]
if len(l) > n:
l2.append('...')
self.msg(self.DEBUG, "%s%s (%s/%s)" % (tab, s, len(l2), len(l)))
if len(l2) > 0:
logging.debug('%s %s' % (tab, l2))
def setup_logging(loglevel=Logging.WARN, verbose=False):
levels = ['debug', 'info', 'warn', 'error']
env_level = os.environ.get('PYCENTRAL', 'warn').lower()
for i in range(len(levels)):
if env_level.find(levels[i]) != -1:
loglevel = i
if verbose:
loglevel = Logging.DEBUG
global logging
logging = Logging(loglevel)
def main():
global_options, action = parse_options(sys.argv[1:])
os.umask(0022)
# setup logging stuff
setup_logging(Logging.WARN, global_options.verbose)
if action.name == 'debhelper' or action.name.startswith('show'):
pass
else:
logging.debug('pycentral ' + ' '.join(sys.argv[1:]))
# check the arguments according to the action called
if action.check_args(global_options):
sys.exit(1)
# run the action and exit
rv = action.run(global_options)
sys.exit(rv)
# call the main routine
if __name__ == '__main__':
main()